﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using SystemHelperLibrary.API.FilesExt;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;

namespace SystemHelperLibrary.API.AviFile
{
	public class AviHelper : IDisposable
	{
		const string AVIFILE32 = "AVIFIL32";
		const int AVIGETFRAMEF_BESTDISPLAYFMT = 1;

		internal static readonly uint streamtypeVIDEO = mmioFOURCC('v', 'i', 'd', 's');
		internal static readonly uint streamtypeAUDIO = mmioFOURCC('a', 'u', 'd', 's');
		internal static readonly uint streamtypeMIDI = mmioFOURCC('m', 'i', 'd', 's');
		internal static readonly uint streamtypeTEXT = mmioFOURCC('t', 'x', 't', 's');

		IntPtr aviFile = IntPtr.Zero;
		IntPtr aviStream = IntPtr.Zero;
		bool disposed = false;

		[DllImport(AVIFILE32)]
		extern internal static void AVIFileInit();

		[DllImport(AVIFILE32)]
		extern internal static void AVIFileExit();

		[DllImport(AVIFILE32)]
		extern internal static uint AVIFileOpen(out IntPtr ppfile, string szFile, FileHelper.OpenFileFlags mode, IntPtr pclsidHandler);

		[DllImport(AVIFILE32)]
		extern internal static int AVIFileRelease(IntPtr pfile);

		[DllImport(AVIFILE32)]
		extern internal static uint AVIFileGetStream(IntPtr pfile, out IntPtr ppavi, uint fccType, int lParam);

		[DllImport(AVIFILE32)]
		extern internal static int AVIStreamRelease(IntPtr pavi);

		[DllImport(AVIFILE32)]
		extern internal static IntPtr AVIStreamGetFrameOpen(IntPtr pavi, int lpbiWanted);

		[DllImport(AVIFILE32)]
		extern internal static IntPtr AVIStreamGetFrame(IntPtr pgf, int lPos);

		[DllImport(AVIFILE32)]
		extern internal static int AVIStreamLength(IntPtr pavi);

		[DllImport(AVIFILE32)]
		extern internal static uint AVIStreamGetFrameClose(IntPtr pget);


		static uint mmioFOURCC(char c0, char c1, char c2, char c3)
		{
			return (uint)c3 << 24 | (uint)c2 << 16 | (uint)c1 << 8 | (uint)c0;
		}

		public static void Initialize()
		{
			AVIFileInit();
		}

		public static void Terminate()
		{
			AVIFileExit();
		}


		public AviHelper(string filename)
		{
			uint result;
			result = AVIFileOpen(out aviFile, filename, FileHelper.OpenFileFlags.OF_READ, IntPtr.Zero);
			if (result != 0)
			{
				Release();
				throw new Exception("AVIFileOpen failure.");
			}

			result = AVIFileGetStream(aviFile, out aviStream, streamtypeVIDEO, 0);
			if (result != 0)
			{
				Release();
				throw new Exception("AVIFileGetStream failure.");
			}
		}

		~AviHelper()
		{
			Dispose(false);
		}

		void Release()
		{
			if (aviStream != IntPtr.Zero)
			{
				AVIStreamRelease(aviStream);
				aviStream = IntPtr.Zero;
			}

			if (aviFile != IntPtr.Zero)
			{
				AVIFileRelease(aviFile);
				aviFile = IntPtr.Zero;
			}
		}

		public int GetMaxFrameCount()
		{
			if (aviStream == IntPtr.Zero)
				throw new InvalidOperationException();
			return AVIStreamLength(aviStream);
		}

		public Bitmap GetFrame(int no)
		{
			if (aviStream == IntPtr.Zero)
				throw new InvalidOperationException();

			IntPtr frame = IntPtr.Zero;
			try
			{
				frame = AVIStreamGetFrameOpen(aviStream, AVIGETFRAMEF_BESTDISPLAYFMT);
				IntPtr pbmi = AVIStreamGetFrame(frame, no);
				return AVIUtil.ToBitmap(pbmi);

			}
			finally
			{
				if (frame != IntPtr.Zero)
					AVIStreamGetFrameClose(frame);
			}
		}

		protected void Dispose(bool disposing)
		{
			if (disposed)
				return;
			disposed = true;
			Release();
		}

		public void Dispose()
		{
			GC.SuppressFinalize(this);
			Dispose(true);
		}
	}

	public class AVIUtil
	{
		private AVIUtil() { }

		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		internal struct BITMAPFILEHEADER
		{
			public ushort bfType;
			public uint bfSize;
			public ushort bfReserved1;
			public ushort bfReserved2;
			public uint bfOffBits;
		}

		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		internal struct BITMAPINFOHEADER
		{
			public uint biSize;
			public int biWidth;
			public int biHeight;
			public ushort biPlanes;
			public ushort biBitCount;
			public uint biCompression;
			public uint biSizeImage;
			public int biXPelsPerMeter;
			public int biYPelsPerMeter;
			public uint biClrUsed;
			public uint biClrImportant;
			public const int BI_RGB = 0;
		}

		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		internal struct AVISTREAMINFOW
		{
			public UInt32 fccType, fccHandler, dwFlags, dwCaps;

			public UInt16 wPriority, wLanguage;

			public UInt32 dwScale, dwRate,
							 dwStart, dwLength, dwInitialFrames, dwSuggestedBufferSize,
							 dwQuality, dwSampleSize, rect_left, rect_top,
							 rect_right, rect_bottom, dwEditCount, dwFormatChangeCount;

			public UInt16 szName0, szName1, szName2, szName3, szName4, szName5,
							 szName6, szName7, szName8, szName9, szName10, szName11,
							 szName12, szName13, szName14, szName15, szName16, szName17,
							 szName18, szName19, szName20, szName21, szName22, szName23,
							 szName24, szName25, szName26, szName27, szName28, szName29,
							 szName30, szName31, szName32, szName33, szName34, szName35,
							 szName36, szName37, szName38, szName39, szName40, szName41,
							 szName42, szName43, szName44, szName45, szName46, szName47,
							 szName48, szName49, szName50, szName51, szName52, szName53,
							 szName54, szName55, szName56, szName57, szName58, szName59,
							 szName60, szName61, szName62, szName63;
		}

		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		internal struct AVICOMPRESSOPTIONS
		{
			public UInt32 fccType;
			public UInt32 fccHandler;
			public UInt32 dwKeyFrameEvery;  // only used with AVICOMRPESSF_KEYFRAMES
			public UInt32 dwQuality;
			public UInt32 dwBytesPerSecond; // only used with AVICOMPRESSF_DATARATE
			public UInt32 dwFlags;
			public IntPtr lpFormat;
			public UInt32 cbFormat;
			public IntPtr lpParms;
			public UInt32 cbParms;
			public UInt32 dwInterleaveEvery;
		}

		internal const uint DIB_RGB_COLORS = 0;
		internal const uint DIB_PAL_COLORS = 1;

		/// <summary> 
		/// 
		/// </summary> 
		/// <param name="pBITMAPINFOHEADER"></param> 
		/// <returns></returns> 
		public static Bitmap ToBitmap(IntPtr pBITMAPINFOHEADER)
		{
			unsafe
			{
				BITMAPINFOHEADER* pbmi = (BITMAPINFOHEADER*)pBITMAPINFOHEADER;
				BITMAPFILEHEADER pbmfi;
				pbmfi.bfType = (int)'M' << 8 | (int)'B';
				pbmfi.bfOffBits = (uint)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
				pbmfi.bfSize = pbmfi.bfOffBits + pbmi->biSizeImage;

				MemoryStream stream = new MemoryStream();
				BinaryWriter bw = new BinaryWriter(stream);
				byte[] data = new byte[sizeof(BITMAPFILEHEADER)];
				Marshal.Copy((IntPtr)(&pbmfi), data, 0, data.Length);
				bw.Write(data);

				data = new byte[sizeof(BITMAPINFOHEADER)];
				Marshal.Copy(pBITMAPINFOHEADER, data, 0, data.Length);
				bw.Write(data);

				data = new byte[pbmi->biSizeImage];
				++pbmi;
				Marshal.Copy((IntPtr)pbmi, data, 0, data.Length);
				bw.Write(data);
				bw.Flush();
				bw.BaseStream.Position = 0;

				return new Bitmap(bw.BaseStream);
			}
		}
	}

	public class AviException : ApplicationException
	{
		private const Int32 AVIERR_BADPARAM = -2147205018;

		public AviException(string s) : base(s) { }
		public AviException(string s, Int32 hr)
			: base(s)
		{
			if (hr == AVIERR_BADPARAM)
			{
				err_msg = "AVIERR_BADPARAM";
			}
			else
			{
				err_msg = "unknown";
			}
		}

		public string ErrMsg()
		{
			return err_msg;
		}
		private string err_msg;
	}

	public class AviWriter
	{
		private int m_pfile = 0;
		private IntPtr m_ps = new IntPtr(0);
		private IntPtr m_psCompressed = new IntPtr(0);
		private UInt32 m_frameRate= 0;
		private int count_ = 0;
		private UInt32 m_width = 0;
		private UInt32 stride_ = 0;
		private UInt32 m_height = 0;
		private UInt32 m_fccType = 1935960438;  // vids
		private UInt32 m_fccHandler = 808810089;// IV50
		//1145656899;  // CVID
		private Bitmap m_bmp;

		[DllImport("avifil32.dll")]
		private static extern void AVIFileInit();

		[DllImport("avifil32.dll")]
		private static extern int AVIFileOpenW(ref int ptr_pfile, [MarshalAs(UnmanagedType.LPWStr)]string fileName, int flags, int dummy);

		[DllImport("avifil32.dll")]
		private static extern int AVIFileCreateStream(int ptr_pfile, out IntPtr ptr_ptr_avi, ref AVIUtil.AVISTREAMINFOW ptr_streaminfo);

		[DllImport("avifil32.dll")]
		private static extern int AVIMakeCompressedStream(out IntPtr ppsCompressed, IntPtr aviStream, ref AVIUtil.AVICOMPRESSOPTIONS ao, int dummy);

		[DllImport("avifil32.dll")]
		private static extern int AVIStreamSetFormat(IntPtr aviStream, Int32 lPos, ref AVIUtil.BITMAPINFOHEADER lpFormat, Int32 cbFormat);

		[DllImport("avifil32.dll")]
		unsafe private static extern int AVISaveOptions(int hwnd, UInt32 flags, int nStreams, IntPtr* ptr_ptr_avi, AVIUtil.AVICOMPRESSOPTIONS** ao);

		[DllImport("avifil32.dll")]
		unsafe private static extern int AVISaveOptionsFree(int nStreams, AVIUtil.AVICOMPRESSOPTIONS* plpOptions);

		[DllImport("avifil32.dll")]
		private static extern int AVIStreamWrite(IntPtr aviStream, Int32 lStart, Int32 lSamples, IntPtr lpBuffer,
													  Int32 cbBuffer, Int32 dwFlags, Int32 dummy1, Int32 dummy2);

		[DllImport("avifil32.dll")]
		private static extern int AVIStreamRelease(IntPtr aviStream);

		[DllImport("avifil32.dll")]
		private static extern int AVIFileRelease(int pfile);

		[DllImport("avifil32.dll")]
		private static extern void AVIFileExit();

		private void CreateStream()
		{
			AVIUtil.AVISTREAMINFOW strhdr = new AVIUtil.AVISTREAMINFOW();
			strhdr.fccType = m_fccType;
			strhdr.fccHandler = m_fccHandler;
			strhdr.dwFlags = 0;
			strhdr.dwCaps = 0;
			strhdr.wPriority = 0;
			strhdr.wLanguage = 0;
			strhdr.dwScale = 1;
			strhdr.dwRate = m_frameRate; // Frames per Second
			strhdr.dwStart = 0;
			strhdr.dwLength = 0;
			strhdr.dwInitialFrames = 0;
			strhdr.dwSuggestedBufferSize = m_height * stride_;
			strhdr.dwQuality = 0xffffffff; //-1;         // Use default
			strhdr.dwSampleSize = 0;
			strhdr.rect_top = 0;
			strhdr.rect_left = 0;
			strhdr.rect_bottom = m_height;
			strhdr.rect_right = m_width;
			strhdr.dwEditCount = 0;
			strhdr.dwFormatChangeCount = 0;
			strhdr.szName0 = 0;
			strhdr.szName1 = 0;

			int hr = AVIFileCreateStream(m_pfile, out m_ps, ref strhdr);

			if (hr != 0)
			{
				throw new AviException("AVIFileCreateStream");
			}
		}

		public Bitmap Open(string fileName, UInt32 frameRate, int width, int height)
		{
			m_frameRate = frameRate;
			m_width = (UInt32)width;
			m_height = (UInt32)height;
			m_bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
			BitmapData bmpDat = m_bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
			stride_ = (UInt32)bmpDat.Stride;
			m_bmp.UnlockBits(bmpDat);
			AVIFileInit();
			int hr = AVIFileOpenW(ref m_pfile, fileName, 4097 /* OF_WRITE | OF_CREATE (winbase.h) */, 0);
			if (hr != 0)
			{
				throw new AviException("error for AVIFileOpenW");
			}
			CreateStream();
			SetOptions();

			return m_bmp;
		}

		unsafe private void SetOptions()
		{
			AVIUtil.AVICOMPRESSOPTIONS opts = new AVIUtil.AVICOMPRESSOPTIONS();
			opts.fccType = 0; //fccType_;
			opts.fccHandler = 0;//fccHandler_;
			opts.dwKeyFrameEvery = 0;
			opts.dwQuality = 0;  // 0 .. 10000
			opts.dwFlags = 0;  // AVICOMRPESSF_KEYFRAMES = 4
			opts.dwBytesPerSecond = 0;
			opts.lpFormat = new IntPtr(0);
			opts.cbFormat = 0;
			opts.lpParms = new IntPtr(0);
			opts.cbParms = 0;
			opts.dwInterleaveEvery = 0;

			AVIUtil.AVICOMPRESSOPTIONS* p = &opts;
			AVIUtil.AVICOMPRESSOPTIONS** pp = &p;

			IntPtr x = m_ps;
			IntPtr* ptr_ps = &x;

			AVISaveOptions(0, 0, 1, ptr_ps, pp);

			// TODO: AVISaveOptionsFree(...)
			AVISaveOptionsFree(1, p);

			int hr = AVIMakeCompressedStream(out m_psCompressed, m_ps, ref opts, 0);
			if (hr != 0)
			{
				throw new AviException("AVIMakeCompressedStream");
			}

			AVIUtil.BITMAPINFOHEADER bi = new AVIUtil.BITMAPINFOHEADER();
			bi.biSize = 40;
			bi.biWidth = (Int32)m_width;
			bi.biHeight = (Int32)m_height;
			bi.biPlanes = 1;
			bi.biBitCount = 24;
			bi.biCompression = 0;  // 0 = BI_RGB
			bi.biSizeImage = stride_ * m_height;
			bi.biXPelsPerMeter = 0;
			bi.biYPelsPerMeter = 0;
			bi.biClrUsed = 0;
			bi.biClrImportant = 0;

			hr = AVIStreamSetFormat(m_psCompressed, 0, ref bi, 40);
			if (hr != 0)
			{
				throw new AviException("AVIStreamSetFormat", hr);
			}
		}

		public void AddFrame()
		{
			BitmapData bmpDat = m_bmp.LockBits(new Rectangle(0, 0, (int)m_width, (int)m_height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

			int hr = AVIStreamWrite(m_psCompressed, count_, 1,
			   bmpDat.Scan0, // pointer to data
			   (Int32)(stride_ * m_height),
			   0, /*16 = AVIIF_KEYFRAMe*/ 0, 0);

			if (hr != 0)
			{
				throw new AviException("AVIStreamWrite");
			}
			m_bmp.UnlockBits(bmpDat);
			count_++;
		}

		public void Close()
		{
			AVIStreamRelease(m_ps);
			AVIStreamRelease(m_psCompressed);

			AVIFileRelease(m_pfile);
			AVIFileExit();
		}

	}

	internal class MandelBrot
	{
		private MandelBrot() { }

		static public void CalcMandelBrot(
							 Bitmap bmp,
							 double xCoordFrom, double yCoordFrom,
							 double xCoordTo, double yCoordTo,
							 Color[] palette)
		{
			int width = bmp.Width;
			int height = bmp.Height;

			int iterMax = palette.Length;

			for (int x = 0; x < width; x++)
			{
				double xCoord = xCoordFrom + (xCoordTo - xCoordFrom) / width * x;
				for (int y = 0; y < height; y++)
				{
					double yCoord = yCoordFrom + (yCoordTo - yCoordFrom) / height * y;

					int cnt = 0;

					Complex c = new Complex(xCoord, yCoord);
					Complex z = new Complex(0, 0);
					Complex z2 = new Complex(0, 0);
					while ((z2.Abs() < 2.0) && (cnt < iterMax))
					{
						z = z2 + c;
						z2 = z * z;
						cnt++;
					}
					if (cnt == iterMax)
					{
						bmp.SetPixel(x, y, Color.FromArgb(0, 0, 0));
					}
					else
					{
						bmp.SetPixel(x, y, palette[cnt]);
					}
				}
			}
		}
	}

	internal struct Complex
	{
		public Complex(double re_, double im_)
		{
			re = re_;
			im = im_;
		}
		public static Complex operator +(Complex arg1, Complex arg2)
		{
			return (new Complex(arg1.re + arg2.re, arg1.im + arg2.im));
		}

		public static Complex operator -(Complex arg1)
		{
			return (new Complex(-arg1.re, -arg1.im));
		}

		public static Complex operator -(Complex arg1, Complex arg2)
		{
			return (new Complex(arg1.re - arg2.re, arg1.im - arg2.im));
		}

		public static Complex operator *(Complex arg1, Complex arg2)
		{
			return (new Complex(
					   arg1.re * arg2.re - arg1.im * arg2.im,
					   arg1.re * arg2.im + arg2.re * arg1.im));
		}

		public static Complex operator /(Complex arg1, Complex arg2)
		{
			double c1, c2, d;
			d = arg2.re * arg2.re + arg2.im * arg2.im;
			if (d == 0)
			{
				return (new Complex(0, 0));
			}
			c1 = arg1.re * arg2.re + arg1.im * arg2.im;
			c2 = arg1.im * arg2.re - arg1.re * arg2.im;
			return (new Complex(c1 / d, c2 / d));
		}

		public double Abs()
		{
			return (Math.Sqrt(re * re + im * im));
		}

		public double Arg()
		{
			double ret = 0;
			if (re != 0)
				ret = (180 / Math.PI) * Math.Atan(im / re);
			return (ret);
		}

		public override string ToString()
		{
			return (String.Format("Complex: ({0}, {1})", re, im));
		}

		private double re;
		private double im;
	}

	public class SomeMovie
	{
		public static void WriteMovie(string aviFilePath)
		{
			try
			{
				Color[] palette = new Color[50];
				for (int i = 0; i < 50; i++)
				{
					palette[i] = Color.FromArgb(i * 4, 255 - i * 4, 50 + i * 2);
				}
				int w = 600;
				int h = 600;
				AviWriter aw = new AviWriter();
				Bitmap bmp = aw.Open(aviFilePath, 25, w, h);

				double f = 1.2;
				double centerX = -0.7454333;
				double centerY = -0.1130211;
				double pctAreaNewImage = 0.9;
				double endWidth_times_2 = 0.0001;

				while (f > endWidth_times_2)
				{

					MandelBrot.CalcMandelBrot(bmp, centerX - f, centerY - f,
												  centerX + f, centerY + f, palette);

					f = Math.Sqrt(pctAreaNewImage * f * f);
					aw.AddFrame();
				}

				aw.Close();
			}
			catch (AviException)
			{ }
		}
	}
}
